package darknet.core.impl.friend;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

import darknet.core.impl.net.NetworkManagerImpl;
import darknet.core.spec.exception.ImportingMyselfException;
import darknet.core.spec.friend.Friend;
import darknet.core.spec.friend.FriendManager;

/**
 * Implementation of a friend manager.
 * 
 * @author yoyo
 */
public class FriendManagerImpl implements FriendManager {

    /** The Keystore */
    private KeyStore keystore;
    
    /** The working directory */
    private File workingDirectory;
    
    /** The Keystore (file) */
    private File keystoreFile;
    
    /** A frien representation of me. */
    private Friend me;
    
    /** The password of the keystore. */
    private String password;
    
    /** The network manager (impl) */
    private NetworkManagerImpl network;
    
    /**
     * Generate a new Friend Manager.
     * 
     * @param workingDirectory Keystore directory
     * @param username FriendManager username (alphanumeric lowercase only)
     * @param password FriendManager password (min 6 char alphanumeric)
     * @return
     * @throws IOException 
     * @throws FileNotFoundException 
     */
    public static FriendManagerImpl generateFriendManager(File workingDirectory,
	    String username, String password) 
    throws FileNotFoundException, IOException {
	
	// Chech parameters.
	if (workingDirectory == null || !workingDirectory.isDirectory()) {
	    throw new IllegalArgumentException("keystoreDir is invalid");
	}
	if (username == null || username.length() == 0 || 
		!username.matches("[a-z0-9]*")) {
	   throw new IllegalArgumentException("username is invalid"); 
	}
	
	if (password == null || !password.matches("[A-Za-z0-9]{6,}")) {
	    throw new IllegalArgumentException("password is invalid");
	}

        // If the keystore already exist, delete it.
        File keystoreFile = new File(
                workingDirectory.getAbsolutePath()
                + File.separator + username + ".keystore");
	
	// Create an new keystore file.
        String[] cmd = {
            "keytool", "-genkeypair",
            "-dname", "cn=" + username,
            "-keypass", password,
            "-keystore", keystoreFile.getAbsoluteFile().toString(),
            "-storepass", password};
	try {
	    Runtime.getRuntime().exec(cmd).waitFor();
	} catch (IOException e) {
	    throw new RuntimeException(e);
	} catch (InterruptedException e) {
	    throw new RuntimeException(e);
	}
	
	return restaureFriendManager(workingDirectory, username, password);
    }

    /**
     * Restaure a previous FriendManager.
     * 
     * @param workingDirectory The keystore directory
     * @param username FriendManager username
     * @param password FriendManager password
     * @return
     * @throws IOException 
     * @throws FileNotFoundException 
     */
    public static FriendManagerImpl restaureFriendManager(
	    File workingDirectory, String username, String password) 
    throws FileNotFoundException, IOException {
        
	return new FriendManagerImpl(workingDirectory, username, password);
    }
    
    /**
     * Initialise a friend manager impl.
     * 
     * @param workingDirectory The directory which contain the keystore.
     * @param username The username to use.
     * @param password The password of the java keystore.
     * 
     * @throws IOException If IO exception occurs while loading keystore.
     * @throws FileNotFoundException If the keystore is not found.
     */
    private FriendManagerImpl(File workingDirectory, String username,
            String password)
    throws FileNotFoundException, IOException {

	// Check parameters
	if (workingDirectory == null || !workingDirectory.isDirectory()) {
	    throw new IllegalArgumentException(
		    "keystoreDir is invalid");
	}
	if (username == null || username.length() == 0) {
	    throw new IllegalArgumentException("username is invalid");
	}
	if (password == null || password.length() == 0) {
	    throw new IllegalArgumentException("password is invalid");
	}

	// working directory.
	this.workingDirectory = workingDirectory;
	
	// Try to create and load a keystore
	File keystoreFile = new File(
		workingDirectory.getAbsoluteFile() + File.separator 
		+ username + ".keystore");

	try {
	    
	    this.keystore = KeyStore.getInstance("JKS");;
	    this.keystore.load(new FileInputStream(keystoreFile), 
		    password.toCharArray());
	    
	} catch (KeyStoreException e) {
	    throw new RuntimeException(e);
	} catch (NoSuchAlgorithmException e) {
	    throw new RuntimeException(e);
	} catch (CertificateException e) {
	    throw new RuntimeException(e);
	}
	
	// Create a friend representation of me.
	try {
	    Certificate cert = keystore.getCertificate("mykey");
	    me = new FriendImpl(username, cert);
	    
	} catch (KeyStoreException e) {
	    throw new RuntimeException(e);
	}
	
	// 
	this.password = password;
	this.keystoreFile = keystoreFile;
    }

    @Override
    public void add(Friend toAdd) throws IOException, ImportingMyselfException {
	
	if(toAdd == null) {
	    throw new IllegalArgumentException("toAdd is invalid (null)");
	}

        // We can't add ourself!
        if (toAdd.getName().equals(this.me.getName())) {
            throw new ImportingMyselfException();
        }

	try {
	    keystore.setCertificateEntry(
		    toAdd.getName(), toAdd.getCertificate());
	    save();
	} catch (KeyStoreException e) {
	    throw new RuntimeException(e);
	}
	
	// Reload SSL Context
	if (network != null) {
	    network.synchronise();
	}
    }

    @Override
    public List<String> get() {

	List<String> friends = new LinkedList<String>();
	
	try {
            // FIXME not case sensitive!
	    Enumeration<String> aliases = this.keystore.aliases(); 
	    String alias = null;
	    while(aliases.hasMoreElements()) {
		alias = aliases.nextElement();
		if(keystore.isCertificateEntry(alias)) {
		    friends.add(alias);
		}
	    }
	} catch (KeyStoreException e) {
	    throw new RuntimeException(e);
	}

	return friends;
    }

    @Override
    public Friend get(String friend) {
	
	if (friend == null || friend.length() == 0) {
	    throw new IllegalArgumentException("friend is invalid");
	}
	
	Friend f = null;
	
	try {
	    Certificate cert = keystore.getCertificate(friend);
	    f = new FriendImpl(friend, cert);
	} catch (KeyStoreException e) {
	    throw new RuntimeException(e);
	}
	
	return f;
    }

    @Override
    public Friend getMe() {
	return me;
    }

    @Override
    public void remove(String toRemove) throws IOException {
	
	try {
	    keystore.deleteEntry(toRemove);
	    save();
	} catch (KeyStoreException e) {
	    throw new RuntimeException(e);
	}
	
	// Reload SSL Context
	if (network != null) {
	    network.synchronise();
	}
    }
    
    /**
     * Get the keystore.
     * @return
     */
    public KeyStore getKeyStore() {
	return keystore;
    }
    
    /**
     * Get the keystore password.
     * @return
     */
    public String getPassword() {
	return password;
    }
    
    /**
     * Get the working directory.
     * @return
     */
    public File getWorkingDirectory() {
	return workingDirectory;
    }
    
    /**
     * Set the network manager.
     * 
     * @param network
     */
    public void setNetworkManager(NetworkManagerImpl network) {
	
	if (network == null) {
	    throw new IllegalArgumentException("network is null");
	}
	this.network = network;
    }

    /**
     * Enregistre l'état actuel du keystore dans un fichier.
     */
    private void save() {
	try {
	    keystore.store(new FileOutputStream(keystoreFile), 
		    password.toCharArray());
	} catch (KeyStoreException e) {
	    throw new RuntimeException(e);
	} catch (NoSuchAlgorithmException e) {
	    throw new RuntimeException(e);
	} catch (CertificateException e) {
	    throw new RuntimeException(e);
	} catch (FileNotFoundException e) {
	    throw new RuntimeException(e);
	} catch (IOException e) {
	    throw new RuntimeException(e);
	}
    }
}
